/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

static const char *windowClassName = "screenbar";

#define GWL_CLASSPOINTER 0

struct EventArg
{
	SHORT size;
	ScreenbarWindow *window;
};

static WNDCLASSEXA windowClass;

/////////////////////////////////////////////////////////////////////////////

ScreenbarWindow::ScreenbarWindow(string prefix, Monitor *monitor)
{
	this->prefix = prefix;
	this->monitor = monitor;
	this->layout = NULL;
	this->window = NULL;
	this->backBuffer = NULL;
	this->currentTransparency = opaque;
	this->alwaysOnTop = false;
	
	this->createAnimation = animationPool->getAnimation(getConfigString("CreateAnimation", "", prefix.c_str()));
	
	if(!getConfigBool("Transparent", false, prefix.c_str())) {
		transparency = opaque;
	} else {
		string transparencyMethod = to_lower(getConfigString("TransparencyMethod", "colorkey", prefix.c_str()));
		if(transparencyMethod == "colorkey")
			transparency = colorKeyed;
		else if(transparencyMethod == "overlay")
			transparency = alphaOverlay;
		else {
			warn(retprintf("Unrecognized transparency type '%s' for panel '%s'.",
				transparencyMethod.c_str(), prefix.c_str()).c_str());
			transparency = opaque;
		}
	}
	
	opacity = getConfigInt("Opacity", 255, prefix.c_str());
	fillColor = getConfigColor("BackgroundColor", 0,0,0, prefix.c_str());
	
	animationDirection = parseDirection(getConfigString("AnimationDirection", "auto", prefix.c_str()));
	
	if(transparency == opaque)
		opacity = 255;
	if(transparency == colorKeyed)
		fillColor = RGB(255,0,255);
}

ScreenbarWindow::~ScreenbarWindow()
{
	delete layout;
	destroyBackBuffer();
	DestroyWindow(window);
}

HWND ScreenbarWindow::initWindow(DWORD style, HWND parent)
{
	EventArg arg = {sizeof(EventArg), this};
	
	window = CreateWindowExA(
		WS_EX_TOOLWINDOW,
		windowClassName,
		prefix.c_str(),
		style,
		visibleRect.left, visibleRect.top, visibleRect.width, visibleRect.height,
		parent,
		NULL, dllInstance, &arg);
	
	if(!window)
		throw std::exception("Failed to create window");
	
	// Mark this window as 'belonging to Litestep' so that we don't try to operate on ourself
	SetWindowLongPtr(window, GWLP_USERDATA, magicDWord);
	
	createBackBuffer();
	
	return window;
}

void ScreenbarWindow::createBackBuffer()
{
	if(backBuffer)
		destroyBackBuffer();
	backBuffer = new BackBuffer(visibleRect.width, visibleRect.height);
}

void ScreenbarWindow::destroyBackBuffer()
{
	if(backBuffer) {
		delete backBuffer;
		backBuffer = NULL;
	}
}
void ScreenbarWindow::initLayout(const ElementContext *context)
{
	string rootElementName = getConfigString("RootElement", NULL, prefix.c_str());
	LayoutElement *rootElement = layoutPool->getElement(rootElementName);
	
	if(!rootElement)
		throw std::exception("Invalid or missing root element");
	
	string sizeFallbacks = getConfigLine("SizeFallbacks", "", prefix.c_str());
	
	layout = new Layout(
		rootElement,
		*context,
		visibleRect,
		sizeFallbacks);
}

void ScreenbarWindow::adjustWindowPos()
{
	destroyBackBuffer();
	createBackBuffer();
	
	SetWindowPos(window, 0,
		visibleRect.left, visibleRect.top, visibleRect.width, visibleRect.height,
		SWP_NOZORDER | SWP_NOACTIVATE);
	
	layout->setRect(visibleRect);
	layout->update();
}

void ScreenbarWindow::show()
{
	setTransparencyMode(transparency);
	
	if(createAnimation) {
		repaint();
		beginAnimation(createAnimation, ANIM_CREATE);
	}
	ShowWindow(window, SW_SHOWNOACTIVATE);
}

LRESULT ScreenbarWindow::handleEvent(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
		case UMSG_REFRESHLAYOUT:
			if(layout->update())
				InvalidateRect(window, NULL, FALSE);
			return 1;
			
		case UMSG_UPDATEOVERLAY:
			repaint();
			return 0;
			
		case WM_PAINT:
			if(transparency == alphaOverlay)
				return DefWindowProc(window, uMsg, wParam, lParam);
			repaint();
			return 0;
		
		case WM_TIMER:
			if(wParam == TIMER_ANIMATEWINDOW) {
				updateAnimation();
				return 0;
			} else if(wParam == TIMER_ANIMATELAYOUT) {
				checkForUpdates();
				return 0;
			}
			break;
	}
	
	return globalWindowProc(window, uMsg, wParam, lParam);
}

void ScreenbarWindow::beginAnimation(Animation *animation, int trigger)
{
	if(!settings->enableAnimations || !settings->enableWindowAnimations)
		return;
	
	// If there's already a window animation started, force it to finish early
	if(animationState.animation)
		finishAnimation();
	
	animationState.animation = animation;
	animationState.trigger = trigger;
	animationState.startTime = getSystemTime();
	animationState.direction = animationDirection;
	
	SetTimer(window, TIMER_ANIMATEWINDOW, settings->animationUpdateInterval, NULL);
	
	if(animation->getRequiredTransparency() == Animation::transAlpha) {
		setTransparencyMode(alphaOverlay);
	} else if(animation->getRequiredTransparency() == Animation::transColorKey) {
		if(currentTransparency != alphaOverlay)
			setTransparencyMode(colorKeyed);
	}
}

void ScreenbarWindow::finishAnimation()
{
	if(!animationState.animation)
		return;
	
	KillTimer(window, TIMER_ANIMATEWINDOW);
	animationState.animation = NULL;
	setTransparencyMode(transparency);
	
	onFinishAnimation(animationState.trigger);
}

void ScreenbarWindow::onFinishAnimation(int trigger)
{
}

void ScreenbarWindow::updateAnimation()
{
	Animation *animation = animationState.animation;
	if(!animation) return;
	
	animationState.updateTime();
	if(animationState.animation->isFinished(&animationState)) {
		finishAnimation();
	}
	
	flipBackBuffer(NULL);
}

/////////////////////////////////////////////////////////////////////////////

void ScreenbarWindow::registerWindowClass()
{
	memset(&windowClass, 0, sizeof(WNDCLASSEXA));
	windowClass.cbSize = sizeof(WNDCLASSEXA);
	windowClass.cbWndExtra = sizeof(VWM*);
	windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	windowClass.lpfnWndProc = ScreenbarWindow::eventHandler;
	windowClass.hInstance = dllInstance;
	windowClass.lpszClassName = windowClassName;

	if (!RegisterClassExA(&windowClass)) {
		// Class could not be registered, try to re-register
		UnregisterClassA(windowClassName, dllInstance);

		if (!RegisterClassExA(&windowClass)) {
			// Still no luck, error out
			fatal("Unable to register Screenbar window class.");
		}
	}
}

void ScreenbarWindow::unregisterWindowClass()
{
	UnregisterClassA(windowClassName, dllInstance);
}

LRESULT CALLBACK ScreenbarWindow::eventHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(uMsg == WM_NCCREATE)
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	
	if (uMsg == WM_CREATE) {
		LPVOID &createParams = LPCREATESTRUCT(lParam)->lpCreateParams;

		if (createParams) {
			ScreenbarWindow *window = ((EventArg*)createParams)->window;
			SetWindowLong(hWnd, GWL_CLASSPOINTER, (LONG)window);
		}
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	
	ScreenbarWindow *window = (ScreenbarWindow*)(GetWindowLong(hWnd, GWL_CLASSPOINTER));
	if(!window) return DefWindowProc(hWnd, uMsg, wParam, lParam);
	
	return window->handleEvent(uMsg, wParam, lParam);
}

/////////////////////////////////////////////////////////////////////////////

void ScreenbarWindow::checkForUpdates()
{
	if(!layout)
		return;
	vwm->deleteEmptyDesktops();
	if(layout->update())
		forceRedraw();
	if(layout->isAnimating()) {
		SetTimer(window, TIMER_ANIMATELAYOUT, settings->animationUpdateInterval, NULL);
	} else {
		KillTimer(window, TIMER_ANIMATELAYOUT);
	}
}

string ScreenbarWindow::getPrefix()
	{ return prefix; }
Layout *ScreenbarWindow::getLayout()
	{ return layout; }
HWND ScreenbarWindow::getWindow()
	{ return window; }
Rect ScreenbarWindow::getRect()
	{ return visibleRect; }
Point ScreenbarWindow::getTopLeft()
	{ return visibleRect.topLeft(); }
Monitor *ScreenbarWindow::getMonitor()
	{ return monitor; }
Direction ScreenbarWindow::getAnimationDirection()
	{ return animationDirection; }